/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Google Code Wiki Viewer.
 *
 * The Initial Developer of the Original Code is Atte Kemppilä.
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 * Atte Kemppilä <atte.kemppila@iki.fi>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */


const EXPORTED_SYMBOLS =
[
    "GoogleCodeWikiParser",
];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");

Cu.import("resource://GoogleCodeWikiViewer/common.jsm");


function GoogleCodeWikiParser()
{
    // empty
}

GoogleCodeWikiParser.prototype =
{
    get parser()
    {
        if (! this._parser)
            this._parser = new parserLoader.Parser();
        
        return this._parser;
    },
    
    parse: function(text, args, callback)
    {
        // wikitext -> html
        let html = this.parser.parse(text);
        
        // template macros
        let _args = {};
        for (let prop in args)
            _args[prop] = args[prop];
        _args.content = html;
        
        // load template and apply macros to template
        let self = this;
        let func = function(template) { callback(self._substitute(template, _args)); };
        templateLoader.load(func);
    },
    
    _substitute: function(template, substitutes)
    {
        let func = function(str, name)
        {
            if (name in substitutes)
                return substitutes[name];
            return str;
        };
        
        return template.replace(/\$\{(\w+)\}/g, func);
    },
}


function ParserLoader()
{
    this._uri = null;
    this._Parser = null;
    this._timestamp = null;
    
    prefs.addObserver("parser", this, false);
}

ParserLoader.prototype =
{
    QueryInterface:
        XPCOMUtils.generateQI([
            Ci.nsIObserver,
        ]),
    
    get uri()
    {
        if (! this._uri)
        {
            let spec = prefs.getComplexValue("parser", Ci.nsISupportsString).data;
            this._uri = Services.io.newURI(spec, null, null);
            if (this._uri.scheme == "file")
                this._uri.QueryInterface(Ci.nsIFileURL);
        }
        
        return this._uri;
    },
    
    get Parser()
    {
        let uri = this.uri;
        
        // remove cached parser instance if file timestamp doesn't match
        if (this._Parser && uri.file && (uri.file.lastModifiedTime != this._timestamp))
        {
            this._Parser = null;
            
            // ff8+: clear mozIJSSubScriptLoader cache
            Services.obs.notifyObservers(null, "startupcache-invalidate", null);
        }
        
        // load parser
        if (! this._Parser)
        {
            try
            {
                let scope = {};
                Services.scriptloader.loadSubScript(uri.spec, scope);
                this._Parser = scope.GoogleCodeWikiParser;
                this._timestamp = (uri.file ? uri.file.lastModifiedTime : -1);
            }
            catch (e)
            {
                // loadSubScript() can throw exceptions without proper location info
                // -> wrap exception in Error so that at least some location info is included
                if (! e.fileName)
                    e = new Error(e);
                
                throw e;
            }
        }
        
        return this._Parser;
    },
    
    // nsIObserver
    observe: function(subject, topic, data)
    {
        this._Parser = null;
        this._uri = null;
    }
}


function TemplateLoader()
{
    this._uri = null;
    this._template = null;
    this._timestamp = null;
    
    prefs.addObserver("template", this, false);
}

TemplateLoader.prototype =
{
    QueryInterface:
        XPCOMUtils.generateQI([
            Ci.nsIObserver,
        ]),
    
    get uri()
    {
        if (! this._uri)
        {
            let spec = prefs.getComplexValue("template", Ci.nsISupportsString).data;
            this._uri = Services.io.newURI(spec, null, null);
            if (this._uri.scheme == "file")
                this._uri.QueryInterface(Ci.nsIFileURL);
        }
        
        return this._uri;
    },
    
    load: function(callback)
    {
        let uri = this.uri;
        
        // remove cached template value if file timestamp doesn't match
        if (this._template && uri.file && (uri.file.lastModifiedTime != this._timestamp))
            this._template = null;
        
        if (this._template)
        {
            callback(this._template);
            return;
        }
        
        let self = this;
        let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
        xhr.overrideMimeType("text/plain");
        xhr.onload = function(e) { self._timestamp = (uri.file ? uri.file.lastModifiedTime : -1); self._onLoad(e, callback); };
        xhr.onerror = function(e) { self._onError(e, callback); };
        xhr.open("GET", uri.spec, true);
        xhr.send(null);
    },
    
    _onLoad: function(e, callback)
    {
        let xhr = e.target;
        this._template = xhr.responseText;
        callback(this._template);
    },
    
    _onError: function(e, callback)
    {
        let xhr = e.target;
        this._template = null;
        callback(xhr.responseText);
    },
    
    // nsIObserver
    observe: function(subject, topic, data)
    {
        this._template = null;
        this._uri = null;
    }
}


this.__defineGetter__("parserLoader", function()
{
    delete this.parserLoader;
    return this.parserLoader = new ParserLoader();
});

this.__defineGetter__("templateLoader", function()
{
    delete this.templateLoader;
    return this.templateLoader = new TemplateLoader();
});
